import React, { useMemo } from 'react';

export interface Box {
  key?: string | number;
  width: number;
  height: number;
}

export interface PositionedBox extends Box {
  /**
   * 计算后的距离左侧位置(position: absolute)
   */
  left: number;
  /**
   * 计算后的距离顶部位置(position: absolute)
   */
  top: number;
  /**
   * 计算后的宽度
   */
  aspectWidth: number;
  /**
   * 计算后的高度
   */
  aspectHeight: number;
  /**
   * 原始数据索引，由于计算过程中可能会改变数据排列顺序，因此需要记录原始索引
   */
  originalIndex?: number;
  /**
   * 列索引
   */
  colIndex: number;
  /**
   * 行索引
   */
  rowIndex: number;
}

export interface WaterfallProps<RecordType extends object = object> {
  /**
   * 数据项的唯一标识符键
   * @default 'key'
   */
  itemKey?: string;
  list: (Box & RecordType)[];
  /**
   * 容器宽度
   */
  width: number;
  padding?:
    | number
    | string
    | [number | string, number | string]
    | [number | string, number | string, number | string, number | string];
  /**
   * 列数
   * @default 4
   */
  cols?: number;
  /**
   * 列间距
   * @default 16
   */
  gap?: number;
  /**
   * 渲染数据项的函数
   * @param item - 瀑布流数据项
   * @param index - 数据项索引
   */
  renderItem?: (item: PositionedBox & RecordType, index: number) => React.ReactNode;
}

/**
 * 瀑布流布局组件
 */
function WaterfallNoMemoized<RecordType extends object = object>(
  props: WaterfallProps<RecordType>
) {
  const { itemKey = 'key', list, width, padding, cols = 4, gap = 16, renderItem } = props;

  /**
   * 上边距
   */
  const pt = useMemo(() => {
    if (typeof padding === 'number' || typeof padding === 'string') {
      return parseFloat(padding as string);
    }
    if (Array.isArray(padding)) {
      return parseFloat((padding[0] ?? 0) as string);
    }
    return 0;
  }, [padding]);

  /**
   * 右边距
   */
  const pr = useMemo(() => {
    if (typeof padding === 'number' || typeof padding === 'string') {
      return parseFloat(padding as string);
    }
    if (Array.isArray(padding)) {
      return parseFloat((padding[1] ?? 0) as string);
    }
    return 0;
  }, [padding]);

  /**
   * 下边距
   */
  const pb = useMemo(() => {
    if (typeof padding === 'number' || typeof padding === 'string') {
      return parseFloat(padding as string);
    }
    if (Array.isArray(padding)) {
      return parseFloat((padding[2] ?? padding[0] ?? 0) as string);
    }
    return 0;
  }, [padding]);

  /**
   * 左边距
   */
  const pl = useMemo(() => {
    if (typeof padding === 'number' || typeof padding === 'string') {
      return parseFloat(padding as string);
    }
    if (Array.isArray(padding)) {
      return parseFloat((padding[3] ?? padding[1] ?? 0) as string);
    }
    return 0;
  }, [padding]);

  /**
   * 列宽度
   */
  const columnWidth = useMemo(() => {
    const totalGap = (cols - 1) * gap; // 总间距
    return (width - totalGap - pl - pr) / cols; // 减去左右边距
  }, [width, cols, gap, pl, pr]);

  /**
   * 初始顶部位置
   */
  const initialTop = pt;

  /**
   * 初始左侧位置
   */
  const initialLeft = pl;

  /**
   * 瀑布流位置排版：按规定列数排布，下一行优先插入上一行高度最小的那一列, 同等高度，优先左侧
   */
  const listWithPosition = useMemo(() => {
    let i = 0;
    let prevRow: (PositionedBox & RecordType)[] = []; // 上一行排版数据
    const _list: (PositionedBox & RecordType)[] = []; // 最终排版结果
    while (i < list.length) {
      const currentOriginalRow: (Box & RecordType)[] = list.slice(i, i + cols);
      let currentRowWithPosition: (PositionedBox & RecordType)[] = [];
      if (prevRow.length === 0) {
        // 第一行直接使用初始排序
        currentRowWithPosition = currentOriginalRow.map((item, index) => ({
          ...item,
          left: initialLeft + index * (columnWidth + gap),
          top: initialTop,
          aspectWidth: columnWidth,
          aspectHeight: (item.height * columnWidth) / item.width,
          originalIndex: index + i,
          colIndex: index,
          rowIndex: 0,
        }));
        _list.push(...currentRowWithPosition);
      } else {
        // 后续行需要重新计算排序位置，优先插入上一行高度最小的那一列索引，相同高度优先左侧
        const sortedPrevRow = [...prevRow].sort((a, b) => {
          // 不同距离高度，按距离高度排序
          if (a.top + a.aspectHeight !== b.top + b.aspectHeight) {
            return a.top + a.aspectHeight - (b.top + b.aspectHeight);
          }
          // 相同距离高度，按列索引排序
          return a.colIndex - b.colIndex;
        });
        sortedPrevRow.forEach((prevItem, index) => {
          const item = currentOriginalRow[index];
          if (item) {
            currentRowWithPosition[prevItem.colIndex] = {
              ...item,
              left: prevItem.left,
              top: prevItem.top + prevItem.aspectHeight + gap,
              aspectWidth: columnWidth,
              aspectHeight: (item.height * columnWidth) / item.width,
              originalIndex: i + index,
              colIndex: prevItem.colIndex,
              rowIndex: Math.floor((i + index) / cols),
            };
          } else {
            // 最后一行可能缺少列数据：如果当前列没有数据，则保持上一行的位置和高度
            currentRowWithPosition[prevItem.colIndex] = {
              width: prevItem.width,
              height: 0, // 高度为 0，表示没有数据
              aspectWidth: columnWidth,
              aspectHeight: 0,
              left: prevItem.left,
              top: prevItem.top + prevItem.aspectHeight + gap,
              // originalIndex: i + index, // 这里不需要 originalIndex，因为没有原始数据
              colIndex: prevItem.colIndex,
              rowIndex: Math.floor((i + index) / cols),
            } as PositionedBox & RecordType;
          }
        });
        _list.push(...currentRowWithPosition);
      }
      prevRow = currentRowWithPosition;
      i += cols;
    }
    return _list;
  }, [cols, columnWidth, gap, initialLeft, initialTop, list]);

  const containerHeight = useMemo(() => {
    if (listWithPosition.length === 0) return 0;
    // 计算最后一行的最大高度
    const lastRow = listWithPosition.slice(-cols);
    const maxTopBody = Math.max(...lastRow.map((item) => item.top + item.aspectHeight));
    return maxTopBody + pb; // 加上下边距
  }, [listWithPosition, pb, cols]);

  return (
    <div
      style={{
        position: 'relative',
        width: `${width}px`,
        height: `${containerHeight}px`,
        padding: `${pt}px ${pr}px ${pb}px ${pl}px`,
        boxSizing: 'border-box',
      }}
    >
      {listWithPosition.map((item, index) => (
        <div
          key={
            (item[itemKey as keyof typeof item] as React.Key) ??
            `empty-${item.rowIndex}-${item.colIndex}`
          }
          className="transition-all duration-300 ease-in-out"
          style={{
            position: 'absolute',
            left: `${item.left}px`,
            top: `${item.top}px`,
            width: `${item.aspectWidth}px`,
            height: `${item.aspectHeight}px`,
          }}
        >
          {renderItem?.(item, index) ?? (
            <div className="size-full bg-gray-100 grid place-items-center overflow-hidden">
              {`${item.aspectWidth}x${item.aspectHeight.toFixed(0)}`}
            </div>
          )}
        </div>
      ))}
    </div>
  );
}

export const Waterfall = React.memo(WaterfallNoMemoized) as typeof WaterfallNoMemoized;
